package com.lib.utils;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.WindowManager;

import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

public class Camera2Helper {
    public static final String CAMERA_ID_FACING_FRONT = "0";
    public static final String CAMERA_ID_FACING_BACK = "1";
    public static final String CAMERA_ID_FACING_EXTERNAL = "2";

    private final static Camera2Helper sIn = new Camera2Helper();

    private final int EXPECT_WIDTH = 1080;
    private final int EXPECT_HEIGHT = 1920;

    //    private final int mFormat = ImageFormat.YUV_420_888;
    private final int mFormat = ImageFormat.JPEG;

    private boolean mEnableLight = true;
    private boolean mNeedPreview;
    private boolean mIsSupportZoom;
    private Rect mCropRect = new Rect();

    private int mWidth;
    private int mHeight;

    private SparseIntArray mOrientation;
    private WindowManager mWm;

    private CameraCaptureSession mCaptureSession;
    private CameraDevice mCameraDevice;
    private CaptureRequest mPreviewRequest;
    private CaptureRequest mCaptureRequest;
    private CaptureRequest mZoomCaptureRequest;
    private ImageReader mImageReader;

    private List<Surface> mSurfaces;
    private StatusListener mStatusListener;
    // 摄像头状态回调
    private volatile InnerCameraDeviceStateCallback mCameraDeviceStateCallback;
    private volatile InnerStateCallback mSessionStateCallback;
    private volatile InnerCaptureCallback mCaptureCallback;
    private volatile File mFile;
    private File mFile0;
    private File mFile1;

    public static Camera2Helper getInstance() {
        if (sIn.mCameraDeviceStateCallback == null) {
            sIn.init();
        }
        return sIn;
    }

    private Camera2Helper() {
        mOrientation = new SparseIntArray();
        mOrientation.append(Surface.ROTATION_0, 90);
        mOrientation.append(Surface.ROTATION_90, 0);
        mOrientation.append(Surface.ROTATION_180, 270);
        mOrientation.append(Surface.ROTATION_270, 180);
    }

    private void init() {
        File parent = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
        parent.mkdirs();
        mFile0 = new File(parent, "capture0.jpegg");
        mFile1 = new File(parent, "capture1.jpegg");

        mCameraDeviceStateCallback = new InnerCameraDeviceStateCallback();
        mSessionStateCallback = new InnerStateCallback();
        mCaptureCallback = new InnerCaptureCallback();
    }

    @SuppressLint("MissingPermission")
    public void openCamera(Context context, String cameraId) {
        if (mCameraDevice != null && mCaptureSession != null) {
            if (mStatusListener != null) {
                mStatusListener.onOpened(this);
            }
            return;
        }
        switch (cameraId) {
            case CAMERA_ID_FACING_FRONT:
                mFile = mFile0;
                break;
            case CAMERA_ID_FACING_BACK:
                mFile = mFile1;
                break;
        }
        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        try {
            CameraManager cm = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
            CameraCharacteristics cc = cm.getCameraCharacteristics(cameraId);
//            configZoomCropRect(cc);
            Size size = getOptimalSize(cc, EXPECT_WIDTH, EXPECT_HEIGHT);

            if (size == null) {
                DisplayMetrics dm = context.getResources().getDisplayMetrics();
                size = new Size(dm.widthPixels, dm.heightPixels);
            }

            mWidth = size.getWidth();
            mHeight = size.getHeight();

            if (XLog.isDebug()) {
                XLog.i("Final PreviewSize: mWidth x mHeight = " + mWidth + "x" + mHeight);
            }
            if (mStatusListener != null) {
                mStatusListener.onPreviewSize(mWidth, mHeight);
            }

            /*打开相机*/
            // 第一个参数指示打开哪个摄像头
            // 第二个参数stateCallback为相机的状态回调接口
            // 第三个参数用来确定Callback在哪个线程执行，传null就是当前线程执行
            cm.openCamera(cameraId, mCameraDeviceStateCallback, null);
        } catch (CameraAccessException | IllegalArgumentException e) {
            if (!cameraId.equals("0")) {
                closeCamera();/*open 1 error*/
                openCamera(context, "0");
            } else {
                if (mStatusListener != null) {
                    mStatusListener.onError(e);
                }
            }
        } catch (Exception e) {
            if (mStatusListener != null) {
                mStatusListener.onError(e);
            }
        }
    }

    private void configZoomCropRect(CameraCharacteristics cameraCharacteristics) throws CameraAccessException {
// 所有摄像头
//            String[] cameraIdList = manager.getCameraIdList();
//            XLog.i("cameraIdList=" + Arrays.toString(cameraIdList));

        Rect rawRect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
        Float maxZoomValue = cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
        mIsSupportZoom = maxZoomValue != null && maxZoomValue > 1;

        if (mIsSupportZoom && rawRect != null) {
            float zoom = 2f;
            float calZoom = zoom - 1f;
            float minW = rawRect.width() / 2f / maxZoomValue;
            float minH = rawRect.height() / 2f / maxZoomValue;

            int deltaX = (int) (minW * calZoom);
            int deltaY = (int) (minH * calZoom);
            mCropRect.set(rawRect.left + deltaX, rawRect.top + deltaY, rawRect.right - deltaX, rawRect.bottom - deltaY);

            if (XLog.isDebug()) {
                XLog.i("MaxZoom:" + maxZoomValue + ", Zoom=" + zoom + "\n" +
                        "rawRect:" + rawRect + ", mCropRect:" + mCropRect.toString());
            }
        }
    }

    // 选择sizeMap中大于并且最接近width和height的size
    private Size getOptimalSize(CameraCharacteristics cc, int destWidth, int destHeight) {
        // 获取StreamConfigurationMap，它是管理摄像头支持的所有输出格式和尺寸
        StreamConfigurationMap map = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        if (map == null) {
            return null;
        }
        Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
        if (outputSizes == null || outputSizes.length == 0) {
            return null;
        }

        List<Size> sizeList = new ArrayList<>();
        for (Size size : outputSizes) {
            int width = size.getWidth();
            int height = size.getHeight();
            if (destWidth > destHeight) {/*横屏*/
                if (width >= destWidth && height >= destHeight) {
                    sizeList.add(size);
                }
            } else {
                if (width >= destHeight && height >= destWidth) {
                    sizeList.add(size);
                }
            }
        }
        if (XLog.isDebug()) {
            XLog.i("SizeList=" + sizeList);
        }
        if (sizeList.size() > 0) {
            return sizeList.get(sizeList.size() - 1);
//            Comparator<Size> comparator = new Comparator<Size>() {
//                @Override
//                public int compare(Size lhs, Size rhs) {
//                    return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());
//                }
//            };
//            return Collections.min(sizeList, comparator);
        }
        return outputSizes[0];
    }

    private void createCaptureSession() {
        List<Surface> outputs = new ArrayList<>();
        if (mStatusListener != null) {
            mStatusListener.addPreviewSurface(outputs, mWidth, mHeight);
        }
        if (outputs.size() > 0) {
            if (mSurfaces == null) {
                mSurfaces = new ArrayList<>();
            }
            mSurfaces.addAll(outputs);
            mNeedPreview = true;
        } else {
            mNeedPreview = false;
        }

        // 最后一个参数代表每次最多获取几帧数据 ImageFormat.YUV_420_888 ImageFormat.JPEG
        mImageReader = ImageReader.newInstance(mWidth, mHeight, mFormat, 2);
//        mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null);/*回调顺序在 onCaptureCompleted 之前*/
        outputs.add(mImageReader.getSurface());
        //创建相机捕获会话
        // 第一个参数是捕获数据的输出Surface列表
        // 第二个参数是CameraCaptureSession的状态回调接口，当它创建好后会回调onConfigured方法
        // 第三个参数用来确定Callback在哪个线程执行，为null的话就在当前线程执行
        try {
            mCameraDevice.createCaptureSession(outputs, mSessionStateCallback, null);
        } catch (CameraAccessException e) {
            if (mStatusListener != null) {
                mStatusListener.onError(e);
            }
        }
    }

    // 创建预览请求的Builder（TEMPLATE_PREVIEW表示预览请求）
    private CaptureRequest createPreviewRequest() throws Exception {
        CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        if (mSurfaces != null) { /*设置预览的显示界面*/
            for (Surface surface : mSurfaces) {
                builder.addTarget(surface);
            }
        }
//       /* builder.addTarget(mImageReader.getSurface());*/
//
//        MeteringRectangle[] meteringRectangles = builder.get(CaptureRequest.CONTROL_AF_REGIONS);
//        if (meteringRectangles != null && meteringRectangles.length > 0) {
//            XLog.w("PreviewRequestBuilder: AF_REGIONS=" + meteringRectangles[0].getRect().toString());
//        }
        builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
//        if (mEnableLight) {
//            builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
//        }
        return builder.build();
    }

    private CaptureRequest createCaptureRequest() throws Exception {
        /*首先我们创建请求拍照的CaptureRequest*/
        CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        if (mSurfaces != null) {
            for (Surface surface : mSurfaces) {
                builder.addTarget(surface);
            }
        }
        builder.addTarget(mImageReader.getSurface());
        //获取屏幕方向
        int rotation = mWm.getDefaultDisplay().getRotation();
        builder.set(CaptureRequest.JPEG_ORIENTATION, mOrientation.get(rotation));
        return builder.build();
    }

    public void stopPreview() {
        if (!mNeedPreview) {
            return;
        }
        XLog.i("......");
        try {
            mCaptureSession.stopRepeating();
        } catch (Throwable ignored) {
        }
    }

    public void startPreview() {
        if (!mNeedPreview) {
            return;
        }
        if (mCameraDevice == null || mCaptureSession == null) {
            return;
        }
        XLog.i("......");
        try {
            if (mPreviewRequest == null) {
                mPreviewRequest = createPreviewRequest();
            }
            mCaptureSession.setRepeatingRequest(mPreviewRequest, null, null);
        } catch (Exception e) {
            if (mStatusListener != null) {
                mStatusListener.onError(e);
            }
        }
    }

    // 拍照
    public void capture() {
        if (mCameraDevice == null || mCaptureSession == null) {
            return;
        }
        if (XLog.isDebug()) {
            XLog.i("......");
        }
        stopPreview();
        try {
            if (mCaptureRequest == null) {
                mCaptureRequest = createCaptureRequest();
            }/* capture  setRepeatingRequest */
            int captureId = mCaptureSession.capture(mCaptureRequest, mCaptureCallback, null);
            if (mStatusListener != null) {
                mStatusListener.onCapture(captureId);
            }
        } catch (Exception e) {
            if (mStatusListener != null) {
                mStatusListener.onError(e);
            }
        }
    }

    public void zoomCapture() {
        if (!mIsSupportZoom) {
            capture();
            return;
        }
        if (mCameraDevice == null || mCaptureSession == null) {
            return;
        }
        if (XLog.isDebug()) {
            XLog.i("......");
        }
        stopPreview();
        try {
            if (mZoomCaptureRequest == null) {
                CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                if (mSurfaces != null) {
                    for (Surface surface : mSurfaces) {
                        builder.addTarget(surface);
                    }
                }
                builder.addTarget(mImageReader.getSurface());
                builder.set(CaptureRequest.SCALER_CROP_REGION, mCropRect);
                mZoomCaptureRequest = builder.build();
            }
            int captureId = mCaptureSession.capture(mZoomCaptureRequest, mCaptureCallback, null);
            if (mStatusListener != null) {
                mStatusListener.onCapture(captureId);
            }
        } catch (Exception e) {
            if (mStatusListener != null) {
                mStatusListener.onError(e);
            }
        }
    }

    private void onCaptureCompleted(int sequenceId) {
        Image image = null;
        try {
            image = mImageReader.acquireLatestImage();
        } catch (Exception e) {
            XLog.w(e.getMessage());
        }
        if (image == null) {
            capture();
            return;
        }

        if (mFormat == ImageFormat.JPEG) {
            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            CloseUtils.closeIOQuietly(image);

            boolean ret = FileUtils.writeFileFromBytes(bytes, mFile);
            if (ret && mStatusListener != null) {
                mStatusListener.onCaptureCompleted(this, mFile);
            }
        } else {
            byte[] nv21Bytes = ImageUtils.yuvToNV21(image, mWidth, mHeight);
            byte[] bytes = ImageUtils.nv21ToBitmapArray(nv21Bytes, mWidth, mHeight);
            CloseUtils.closeIOQuietly(image);

            if (bytes == null || bytes.length == 0) {
                return;
            }

            boolean ret = FileUtils.writeFileFromBytes(bytes, mFile);
            if (ret) {
                mStatusListener.onCaptureCompleted(this, mFile);
            }
        }
    }

    public Camera2Helper setStatusListener(StatusListener l) {
        mStatusListener = l;
        return this;
    }

    public boolean isOpened() {
        return mCameraDevice != null && mCaptureSession != null;
    }

    public void closeCamera() {
        XLog.i("...");
        if (mCaptureSession != null) {
            stopPreview();
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (mImageReader != null) {
            mImageReader.close();
            mImageReader = null;
        }
        if (mSurfaces != null) {
            mSurfaces.clear();
            mSurfaces = null;
        }
        mPreviewRequest = null;
        mCaptureRequest = null;
        mZoomCaptureRequest = null;
    }

    public void release() {
        closeCamera();
        mCameraDeviceStateCallback = null;
        mSessionStateCallback = null;
        mCaptureCallback = null;
        mStatusListener = null;
        mFile0 = null;
        mFile1 = null;
        mFile = null;
    }

    public interface StatusListener {
        void onPreviewSize(int width, int height);

        void addPreviewSurface(List<Surface> outputs, int width, int height);

        void onOpened(Camera2Helper ch);
//        void onPreviewCompleted();

        void onCapture(int captureId);

        void onCaptureCompleted(Camera2Helper ch, File f);

        void onError(Exception e);

//        void onBitmapArr(byte[] bytes);
    }

    private class InnerCameraDeviceStateCallback extends CameraDevice.StateCallback {
        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            createCaptureSession();
        }

        public void onClosed(CameraDevice camera) {
//            if (mStatusListener != null) {
//                mStatusListener.onClosed();
//            }
        }

        @Override
        public void onDisconnected(CameraDevice camera) {
            closeCamera();
        }

        @Override
        public void onError(CameraDevice camera, int error) {
            closeCamera();
            if (mStatusListener != null) {
                mStatusListener.onError(new Exception("error=" + error));
            }
        }
    }

    private class InnerStateCallback extends CameraCaptureSession.StateCallback {
        @Override
        public void onConfigured(CameraCaptureSession session) {
            mCaptureSession = session;
            if (mStatusListener != null) {
                mStatusListener.onOpened(Camera2Helper.this);
            }
        }

        @Override
        public void onConfigureFailed(CameraCaptureSession session) {
            if (mStatusListener != null) {
                mStatusListener.onError(new Exception("onConfigureFailed=" + session));
            }
        }
    }

    private class InnerCaptureCallback extends CameraCaptureSession.CaptureCallback {

        @Override
        public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
            Integer integer = request.get(CaptureRequest.CONTROL_CAPTURE_INTENT);

            if (XLog.isDebug()) {
                List<CaptureResult> list = result.getPartialResults();
                for (CaptureResult captureResult : list) {
                    List<CaptureResult.Key<?>> keys = captureResult.getKeys();
                    for (CaptureResult.Key<?> key : keys) {
                        Object o = captureResult.get(key);
                        XLog.i(key + "-->" + o);
                    }
                }
            }
            if (integer == null) {
                return;
            }
            switch (integer) {
//                case CameraDevice.TEMPLATE_PREVIEW:
//                    XLog.i("onPreviewed:" + mCallbackMs);
//                    if (mStatusListener != null) {
//                        mStatusListener.onPreviewCompleted();
//                    }
//                    break;
                case CameraDevice.TEMPLATE_STILL_CAPTURE:
//                    if (mStatusListener != null) {
//                        mStatusListener.onCaptureCompleted(session, request, result);
//                    }
                    int sequenceId = result.getSequenceId();
                    Camera2Helper.this.onCaptureCompleted(sequenceId);
                    break;
            }
        }
    }
}